# log_analyze.py
# 日志解析工具

import sys
import re
import os
import numpy as np

from math import dist
from datetime import datetime
from PyQt5.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QTextEdit,
    QSlider,
    QLabel,
    QSplitter,
    QScrollBar,
    QFrame,
    QSizePolicy,
    QPushButton,
    QGroupBox,
)
from PyQt5.QtGui import QPainter, QPen, QColor, QBrush, QFont, QTextCursor, QIcon
from PyQt5.QtCore import Qt, QTimer, QRectF, QPointF


class LogParser:
    def __init__(self, log_file_path):
        self.log_file_path = log_file_path
        self.log_entries = []
        self.robot_states = {}  # 存储每个时间点的机器人状态
        self.timestamps = []

    def parse_log(self):
        """解析日志文件，提取时间戳和机器人状态信息"""
        if not os.path.exists(self.log_file_path):
            print(f"日志文件不存在: {self.log_file_path}")
            return False

        with open(self.log_file_path, "r", encoding="utf-8") as f:
            current_timestamp = None
            current_message = []
            for line in f:
                match = re.match(
                    r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) - (.*)", line.strip()
                )
                if match:
                    # 保存上一个
                    if current_timestamp and current_message:
                        full_message = "\n".join(current_message)
                        self.log_entries.append((current_timestamp, full_message))
                    timestamp_str = match.group(1)
                    message_part = match.group(2)
                    try:
                        current_timestamp = datetime.strptime(
                            timestamp_str, "%Y-%m-%d %H:%M:%S,%f"
                        )
                    except:
                        current_timestamp = None
                        continue
                    current_message = [message_part]
                else:
                    if current_message:
                        current_message.append(line.strip())
            # 最后一个
            if current_timestamp and current_message:
                full_message = "\n".join(current_message)
                self.log_entries.append((current_timestamp, full_message))

        current_state = {}
        in_status_block = False
        current_timestamp = None
        current_robot = None

        for timestamp, message in self.log_entries:
            lines = message.split("\n")
            for msg_line in lines:
                msg_line = msg_line.strip()
                if not msg_line:
                    continue

                # 检查是否开始状态监控块
                if "===== 机器人状态监控 =====" in msg_line:
                    in_status_block = True
                    current_state = {"timestamp": timestamp}
                    current_timestamp = timestamp
                    continue

                # 检查是否结束状态监控块
                if "==============================" in msg_line:
                    in_status_block = False
                    current_robot = None
                    if current_state:
                        self.robot_states[current_timestamp] = current_state.copy()
                        self.timestamps.append(current_timestamp)
                    continue

                # 如果在状态监控块中，解析
                if in_status_block:
                    if msg_line.startswith("游戏状态:"):
                        current_state["game_active"] = "进行中" in msg_line
                    elif msg_line.startswith("捕获次数:"):
                        match = re.search(r"捕获次数: (\d+)", msg_line)
                        if match:
                            current_state["capture_count"] = int(match.group(1))
                    elif msg_line.startswith("预测步长:"):
                        match = re.search(r"预测步长: (\d+)", msg_line)
                        if match:
                            current_state["predict_step"] = int(match.group(1))
                    elif msg_line.startswith("逃避者 "):
                        match = re.search(r"逃避者 (\d+):", msg_line)
                        if match:
                            current_robot = "evader"
                            current_state[current_robot] = {}
                    elif msg_line.startswith("逃避者: 未连接"):
                        current_robot = None
                    elif msg_line.startswith("追捕者 "):
                        match = re.search(r"追捕者 (\d+):", msg_line)
                        if match:
                            rid = int(match.group(1))
                            current_robot = f"pursuer{rid}"
                            current_state[current_robot] = {}
                        if "未连接" in msg_line:
                            current_robot = None
                    elif current_robot:
                        self.parse_robot_info(msg_line, current_robot, current_state)

        # 按时间排序
        self.timestamps.sort()

        # 输出解析结果
        ## 输出时间起点、终点、跨时
        if self.timestamps:
            start_time = self.timestamps[0]
            end_time = self.timestamps[-1]
            duration = end_time - start_time
            print(f"状态时间起点: {start_time}")
            print(f"状态时间终点: {end_time}")
            print(f"状态跨时: {duration.total_seconds()} 秒")

        if self.log_entries:
            log_start = self.log_entries[0][0]
            log_end = self.log_entries[-1][0]
            log_duration = log_end - log_start
            print(f"日志起点: {log_start}")
            print(f"日志终点: {log_end}")
            print(f"日志跨时: {log_duration.total_seconds()} 秒")

        ## 输出日志总条数和各类型数据的条数(状态更新，连接，断开等)
        print(f"日志总条数: {len(self.log_entries)}")
        status_blocks = len(self.timestamps)
        print(f"状态更新块数: {status_blocks}")
        connections = sum(1 for _, msg in self.log_entries if "连接成功" in msg)
        print(f"连接数: {connections}")
        disconnections = sum(
            1 for _, msg in self.log_entries if "断开连接" in msg or "[清理]" in msg
        )
        print(f"断开数: {disconnections}")
        heartbeats = sum(1 for _, msg in self.log_entries if "心跳" in msg)
        print(f"心跳数: {heartbeats}")
        strategies = sum(1 for _, msg in self.log_entries if "策略" in msg)
        print(f"策略计算数: {strategies}")
        return True

    def parse_robot_info(self, line, robot_type, state):
        """解析机器人信息"""
        if robot_type not in state:
            state[robot_type] = {}

        # 解析位置和节点
        if line.startswith("位置:"):
            match_pos = re.search(r"位置: \(([\d.-]+), ([\d.-]+)\)", line)
            if match_pos:
                x, y = float(match_pos.group(1)), float(match_pos.group(2))
                state[robot_type]["position"] = (x, y)
            match_node = re.search(r"节点: (\d+)", line)
            if match_node:
                state[robot_type]["node"] = int(match_node.group(1))

        # 解析目标和目标节点
        if line.startswith("目标:"):
            match_target = re.search(r"目标: \(([\d.-]+), ([\d.-]+)\)", line)
            if match_target:
                x, y = float(match_target.group(1)), float(match_target.group(2))
                state[robot_type]["target"] = (x, y)
            elif "None" in line:
                state[robot_type]["target"] = None
            match_target_node = re.search(r"目标节点: (\d+)", line)
            if match_target_node:
                state[robot_type]["target_node"] = int(match_target_node.group(1))
            elif "None" in line:
                state[robot_type]["target_node"] = None

        # 解析状态和最后更新
        if line.startswith("状态:"):
            match_status = re.search(r"状态: (\w+)", line)
            if match_status:
                state[robot_type]["status"] = match_status.group(1)
            match_update = re.search(r"最后更新: ([\d.]+)秒前", line)
            if match_update:
                state[robot_type]["last_update"] = float(match_update.group(1))

        # 解析最后心跳
        if line.startswith("最后心跳:"):
            match_heartbeat = re.search(r"最后心跳: ([\d.]+)秒前", line)
            if match_heartbeat:
                state[robot_type]["last_heartbeat"] = float(match_heartbeat.group(1))

    def get_state_at_time(self, timestamp):
        """获取指定时间点的状态"""
        # 找到指定时间点之前的最新状态
        for i in range(len(self.timestamps) - 1, -1, -1):
            if self.timestamps[i] <= timestamp:
                return self.robot_states.get(self.timestamps[i])
        return None

    def get_log_index_at_time(self, timestamp):
        """获取指定时间点之前的最后一条日志的索引"""
        for i in range(len(self.log_entries) - 1, -1, -1):
            if self.log_entries[i][0] <= timestamp:
                return i
        return 0


class GraphManager:
    def __init__(self):
        self.node_coordinates = []  # 节点坐标列表
        self.adj_matrix = []  # 邻接矩阵
        self.dist_3d = []  # 三维距离矩阵
        self.f_matrix = []  # Floyd-Warshall 最短路径矩阵
        self.node_num = 0
        # 加载图数据
        self.load_graph_data()

    def load_graph_data(self):
        """加载图数据（邻接矩阵和预计算的距离矩阵）"""
        root = "../datas/"
        try:
            # 加载邻接矩阵
            adj_matrix_path = os.path.join(root, "adj_matrix.txt")
            if not os.path.exists(adj_matrix_path):
                raise FileNotFoundError(f"邻接矩阵文件不存在: {adj_matrix_path}")
            with open(adj_matrix_path, "r") as f:
                self.node_num = int(f.readline().strip())
                self.adj_matrix = np.zeros((self.node_num, self.node_num), dtype=int)
                for i in range(self.node_num):
                    row = list(map(int, f.readline().split()))
                    self.adj_matrix[i] = row
            # 加载节点坐标
            points_path = os.path.join(root, "points.txt")
            self.load_node_coordinates(points_path)

        except Exception as e:
            print(f"加载图数据错误: {e}")
            raise  # 重新抛出异常以便上层处理

    def load_node_coordinates(self, filename):
        """从文件加载节点坐标，文件不存在时抛出异常"""
        if not os.path.exists(filename):
            raise FileNotFoundError(f"节点坐标文件不存在: {filename}")
        try:
            self.node_coordinates = []
            with open(filename, "r") as f:
                for line in f:
                    # 尝试两种格式：逗号分隔或空格分隔
                    if "," in line:
                        parts = line.strip().split(",")
                    else:
                        parts = line.strip().split()
                    if len(parts) >= 2:
                        x = float(parts[0])
                        y = float(parts[1])
                        self.node_coordinates.append((x, y))
            # 验证节点数量是否匹配
            if len(self.node_coordinates) != self.node_num:
                raise ValueError(
                    f"坐标文件节点数不匹配: 坐标文件 {len(self.node_coordinates)} 个节点, "
                    f"邻接矩阵 {self.node_num} 个节点"
                )
            print(f"成功加载 {len(self.node_coordinates)} 个节点的坐标")
            return True
        except Exception as e:
            print(f"加载节点坐标错误: {e}")
            raise  # 重新抛出异常

    def position_to_node(self, x, y):
        """将实际坐标映射到最近的节点"""
        min_dist = float("inf")
        closest_node = 0
        for idx, (nx, ny) in enumerate(self.node_coordinates):
            d = dist((x, y), (nx, ny))
            if d < min_dist:
                min_dist = d
                closest_node = idx
        return closest_node

    def node_to_position(self, node_id):
        """将节点ID转换为实际坐标"""
        if 0 <= node_id < len(self.node_coordinates):
            return self.node_coordinates[node_id]
        return (0.0, 0.0)  # 默认位置

    def get_neighbors(self, node_id):
        """获取节点的邻居（不包括自身）"""
        neighbors = []
        for j in range(self.node_num):
            if self.adj_matrix[node_id][j] == 1:
                neighbors.append(j)
        return neighbors


class GraphMapWidget(QWidget):
    def __init__(self, graph_data=None, parent=None):
        super().__init__(parent)
        self.graph = graph_data
        self.robots = []
        self.capture_count = 0
        self.scale_factor = 1.0
        self.offset_x = 0
        self.offset_y = 0

        # 定义角色颜色
        self.role_colors = {
            "evader": QColor(255, 0, 0),  # 红色 - 逃避者
            "pursuer1": QColor(0, 0, 255),  # 蓝色 - 追捕者1
            "pursuer2": QColor(0, 100, 255),  # 浅蓝色 - 追捕者2
        }

        self.setMinimumSize(400, 400)

    def update_state(self, state):
        """根据状态更新机器人显示"""
        if not state:
            self.robots = []
            self.capture_count = 0
            self.update()
            return

        self.robots = []
        self.capture_count = state.get("capture_count", 0)

        # 添加逃避者
        if "evader" in state:
            evader = state["evader"]
            self.robots.append(
                {
                    "id": 0,
                    "role": "evader",
                    "position": evader.get("position", None),
                    "target": evader.get("target", None),
                    "status": evader.get("status", "unknown"),
                }
            )

        # 添加追捕者
        for i in [1, 2]:
            pursuer_key = f"pursuer{i}"
            if pursuer_key in state:
                pursuer = state[pursuer_key]
                self.robots.append(
                    {
                        "id": i,
                        "role": pursuer_key,
                        "position": pursuer.get("position", None),
                        "target": pursuer.get("target", None),
                        "status": pursuer.get("status", "unknown"),
                    }
                )

        self.update()

    def world_to_pixel(self, x, y):
        """将世界坐标转换为像素坐标"""
        if (
            not hasattr(self.graph, "node_coordinates")
            or not self.graph.node_coordinates
        ):
            return int(x), int(y)

        scaled_x = int(x * self.scale_factor + self.offset_x)
        scaled_y = int(y * self.scale_factor + self.offset_y)
        flipped_y = self.height() - scaled_y
        return scaled_x, flipped_y

    def paintEvent(self, event):
        """绘制地图和机器人"""
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        # 绘制背景
        painter.fillRect(self.rect(), QColor(240, 240, 240))

        # 如果没有图数据，显示提示
        if (
            not hasattr(self.graph, "node_coordinates")
            or not self.graph.node_coordinates
        ):
            painter.setPen(QPen(Qt.black))
            painter.setFont(QFont("Arial", 12))
            painter.drawText(self.rect(), Qt.AlignCenter, "没有地图数据可用")
            return

        # 绘制节点和边
        node_radius = 3
        for i, (x, y) in enumerate(self.graph.node_coordinates):
            px, py = self.world_to_pixel(x, y)

            # 绘制边
            for j in range(i + 1, len(self.graph.node_coordinates)):
                if (
                    hasattr(self.graph, "adj_matrix")
                    and i < len(self.graph.adj_matrix)
                    and j < len(self.graph.adj_matrix[i])
                ):
                    if self.graph.adj_matrix[i][j] == 1:
                        jx, jy = self.graph.node_coordinates[j]
                        pjx, pjy = self.world_to_pixel(jx, jy)
                        painter.setPen(QPen(QColor(180, 180, 180), 1))
                        painter.drawLine(px, py, pjx, pjy)

            # 绘制节点
            painter.setPen(QPen(QColor(80, 80, 80), 1))
            painter.setBrush(QBrush(QColor(220, 220, 220)))
            painter.drawEllipse(
                px - node_radius, py - node_radius, node_radius * 2, node_radius * 2
            )

        # 绘制机器人
        for robot in self.robots:
            if "position" not in robot or robot["position"] is None:
                continue

            # 解析位置
            try:
                x, y = robot["position"]
            except:
                continue

            px, py = self.world_to_pixel(x, y)

            # 根据角色选择颜色
            role = robot.get("role", "unknown")
            color = self.role_colors.get(role, QColor(128, 128, 128))

            # 绘制机器人位置
            robot_radius = 10
            painter.setPen(QPen(color.darker(), 2))
            painter.setBrush(QBrush(color.lighter(150)))
            painter.drawEllipse(
                px - robot_radius, py - robot_radius, robot_radius * 2, robot_radius * 2
            )

            # 绘制机器人ID和角色
            painter.setPen(QPen(Qt.black))
            painter.setFont(QFont("Arial", 9, QFont.Bold))
            role_symbol = "E" if role == "evader" else "P" if "pursuer" in role else "?"
            painter.drawText(px - 15, py - 15, f"{robot['id']}{role_symbol}")

            # 绘制目标位置（如果有）
            if "target" in robot and robot["target"] is not None:
                try:
                    tx, ty = robot["target"]

                    tpx, tpy = self.world_to_pixel(tx, ty)

                    # 绘制目标点
                    painter.setPen(QPen(color, 2, Qt.DashLine))
                    painter.drawLine(px, py, tpx, tpy)
                    painter.setPen(QPen(color.darker(), 2))
                    painter.setBrush(QBrush(Qt.NoBrush))
                    painter.drawEllipse(tpx - 6, tpy - 6, 12, 12)
                except:
                    pass

            # 绘制状态指示器
            status = robot.get("status", "unknown")
            if status == "active":
                status_color = Qt.green
            elif status == "connecting":
                status_color = Qt.yellow
            elif status == "disconnected":
                status_color = Qt.red
            else:
                status_color = Qt.gray

            painter.setPen(QPen(status_color, 2))
            painter.drawEllipse(
                px - robot_radius - 2,
                py - robot_radius - 2,
                (robot_radius + 2) * 2,
                (robot_radius + 2) * 2,
            )

        # 绘制标题和捕获次数
        painter.setPen(QPen(Qt.black))
        painter.setFont(QFont("Arial", 14, QFont.Bold))
        painter.drawText(20, 30, "追捕游戏 - 日志回放")
        painter.setFont(QFont("Arial", 12))
        painter.drawText(20, 60, f"捕获次数: {self.capture_count}")

    def resizeEvent(self, event):
        """窗口大小改变时重新计算缩放因子"""
        super().resizeEvent(event)

        if (
            not hasattr(self.graph, "node_coordinates")
            or not self.graph.node_coordinates
        ):
            return

        # 找到节点的边界
        min_x = min(x for x, y in self.graph.node_coordinates)
        max_x = max(x for x, y in self.graph.node_coordinates)
        min_y = min(y for x, y in self.graph.node_coordinates)
        max_y = max(y for x, y in self.graph.node_coordinates)

        # 计算缩放因子
        width_ratio = (self.width() - 100) / (max_x - min_x) if max_x - min_x > 0 else 1
        height_ratio = (
            (self.height() - 100) / (max_y - min_y) if max_y - min_y > 0 else 1
        )
        self.scale_factor = min(width_ratio, height_ratio)

        # 计算偏移量以使图形居中
        self.offset_x = (
            self.width() - (max_x - min_x) * self.scale_factor
        ) / 2 - min_x * self.scale_factor
        self.offset_y = (
            self.height() - (max_y - min_y) * self.scale_factor
        ) / 2 - min_y * self.scale_factor


class LogAnalyzerUI(QMainWindow):
    def __init__(self, log_file_path, graph_data=None):
        super().__init__()
        print("开始解析日志文件：", log_file_path)
        self.log_file_path = log_file_path
        self.graph_data = graph_data
        self.log_parser = LogParser(log_file_path)
        print("日志解析完成")

        self.setWindowTitle("追捕游戏日志分析工具")
        self.setGeometry(100, 100, 1200, 800)

        # 解析日志
        if not self.log_parser.parse_log():
            print("日志解析失败")
            return

        print("创建布局...")
        # 创建主布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # 创建顶部水平分割器（地图和日志文本）
        top_splitter = QSplitter(Qt.Horizontal)

        # 左侧：地图可视化
        self.map_widget = GraphMapWidget(self.graph_data)
        top_splitter.addWidget(self.map_widget)

        # 右侧：日志文本显示
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setFont(QFont("Courier New", 9))
        top_splitter.addWidget(self.log_text)

        # 设置分割比例
        top_splitter.setSizes([600, 600])

        # 底部：时间轴控件
        bottom_widget = QWidget()
        bottom_layout = QVBoxLayout(bottom_widget)

        # 时间轴标签
        self.time_label = QLabel("时间: 00:00:00")
        bottom_layout.addWidget(self.time_label)

        # 时间轴滑动条（横向）
        self.time_slider = QSlider(Qt.Horizontal)
        self.time_slider.setMinimum(0)
        if self.log_parser.timestamps:
            self.time_slider.setMaximum(len(self.log_parser.timestamps) - 1)
        else:
            self.time_slider.setMaximum(0)
        self.time_slider.valueChanged.connect(self.on_time_changed)
        bottom_layout.addWidget(self.time_slider)

        # 播放控件布局
        control_layout = QHBoxLayout()
        self.play_button = QPushButton("Play")
        self.play_button.clicked.connect(self.toggle_play)
        control_layout.addWidget(self.play_button)

        self.speed_down_button = QPushButton("<< Speed Down")
        self.speed_down_button.clicked.connect(self.speed_down)
        control_layout.addWidget(self.speed_down_button)

        self.speed_up_button = QPushButton("Speed Up >>")
        self.speed_up_button.clicked.connect(self.speed_up)
        control_layout.addWidget(self.speed_up_button)

        self.speed_label = QLabel("Speed: 1.0x")
        control_layout.addWidget(self.speed_label)

        bottom_layout.addLayout(control_layout)

        # 添加控件到主布局
        main_layout.addWidget(top_splitter, 4)  # 顶部占4/5空间
        main_layout.addWidget(bottom_widget, 1)  # 底部占1/5空间

        # 填充日志文本
        self.entry_line_starts = []
        self.populate_log_text()

        # 设置初始时间
        if self.log_parser.timestamps:
            self.time_slider.setValue(0)
            self.on_time_changed(0)

        # 自动播放定时器
        self.play_timer = QTimer(self)
        self.play_timer.timeout.connect(self.advance_frame)
        self.is_playing = False
        self.speed = 1.0
        self.base_interval = 1000  # 基础间隔1秒
        self.update_timer_interval()

        print("布局创建完成")

    def populate_log_text(self):
        """填充日志文本"""
        self.log_text.clear()
        current_line = 0
        self.entry_line_starts = []
        for timestamp, message in self.log_parser.log_entries:
            time_str = timestamp.strftime("%H:%M:%S")
            lines = message.split("\n")
            display_lines = []
            for sub in lines:
                if sub.strip():
                    display_lines.append(f"{time_str} - {sub}")
            display_text = "\n".join(display_lines)
            self.log_text.append(display_text)
            self.entry_line_starts.append(current_line)
            lines_added = len(display_lines)
            current_line += lines_added

    def on_time_changed(self, value):
        """时间轴变化时的回调"""
        if not self.log_parser.timestamps:
            return

        # 获取对应时间戳
        if value < len(self.log_parser.timestamps):
            timestamp = self.log_parser.timestamps[value]

            # 更新地图显示
            state = self.log_parser.get_state_at_time(timestamp)
            self.map_widget.update_state(state)

            # 更新日志文本滚动位置
            log_index = self.log_parser.get_log_index_at_time(timestamp)
            if log_index < len(self.entry_line_starts):
                line_num = self.entry_line_starts[log_index]
                cursor = QTextCursor(
                    self.log_text.document().findBlockByLineNumber(line_num)
                )
                self.log_text.setTextCursor(cursor)
                self.log_text.ensureCursorVisible()

            # 更新时间标签
            time_str = timestamp.strftime("%H:%M:%S")
            self.time_label.setText(f"时间: {time_str}")

    def toggle_play(self):
        if self.is_playing:
            self.play_timer.stop()
            self.play_button.setText("Play")
            self.is_playing = False
        else:
            self.play_timer.start()
            self.play_button.setText("Pause")
            self.is_playing = True

    def speed_up(self):
        self.speed *= 2
        if self.speed > 16:
            self.speed = 16
        self.update_timer_interval()
        self.speed_label.setText(f"Speed: {self.speed:.1f}x")

    def speed_down(self):
        self.speed /= 2
        if self.speed < 0.25:
            self.speed = 0.25
        self.update_timer_interval()
        self.speed_label.setText(f"Speed: {self.speed:.1f}x")

    def update_timer_interval(self):
        interval = int(self.base_interval / self.speed)
        self.play_timer.setInterval(interval)

    def advance_frame(self):
        value = self.time_slider.value()
        max_value = self.time_slider.maximum()
        if value < max_value:
            self.time_slider.setValue(value + 1)
        else:
            self.toggle_play()  # 到达末尾停止播放


if __name__ == "__main__":
    print("程序启动...")

    app = QApplication(sys.argv)

    # 日志文件路径
    log_file_path = "pursuit_game_server.log"

    print("开始加载图数据")
    # 创建图管理器
    try:
        graph_data = GraphManager()
    except Exception as e:
        print(f"加载图数据失败: {e}")
        # 创建空的图数据
        graph_data = type("MockGraph", (), {})()
        graph_data.node_coordinates = []
        graph_data.adj_matrix = []

    print("图数据加载完成")
    # 创建UI
    ui = LogAnalyzerUI(log_file_path, graph_data)
    ui.show()

    sys.exit(app.exec_())
